#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "erros.h"
#include "hash_table.h"

/** Inicializa a tabela de hash
 * @param hash Apontador para a estrutura que armazena a tabela de hash
 * @param tamanho tamanho da tabela
 * @param funcao_hash funcao de dispersao
 * @param getKey funfao que extrai a chave a partir da da informacao guardada na tabela
 * @param compKey funcao que compara duas chaves e devolve zero no caso de serem iguais
 * @return 0 em sucesso, exit(ERRO) caso contrario
 */
int hash_table_init (HASH_TABLE **hash, int tamanho,
		unsigned int 	(*funcao_hash)	(void *),
		void *			(*getKey)		(void *),
		int 			(*compKey)		(void *,void *))
{

    (*hash)  = (HASH_TABLE *) calloc (1, sizeof (HASH_TABLE));
    if(*hash == NULL) exit(NULL_PTR);

    /* Alocar array de apontadores para HASH_NAME */
    (*hash)->table	= (NODO **) calloc (tamanho, sizeof (NODO));
    if((*hash)->table == NULL) exit(NO_MEM);

    (*hash)->ocupados	= 0;
    (*hash)->tamanho	= tamanho;
    (*hash)->hash	= funcao_hash;
    (*hash)->getKey	= getKey;
    (*hash)->compKey	= compKey;

    return 0;
}/**
 * Verifica se uma tabela esta vazia
 */

/**
 * Verifica se uma tabela esta vazia
 */
int hash_table_ocupados(HASH_TABLE *hash_point)	{
	return (hash_point->ocupados);
}

/**
 * Insere um utilizador da tabela de hash
 * @param hash_point Estrtura da tabela de hash
 * @param str Nome do utilizador a inserir
 * @param inf Informacao do utilizador a inserir
 */
int hash_table_inserir (HASH_TABLE *hash_point, void* info)	{

	void * key = (hash_point->getKey)(info);
	unsigned long hash = (hash_point->hash)(key);
	hash = hash % hash_point->tamanho;

	NODO *aux = hash_point->table[hash];
	NODO *new = (NODO *) calloc (1,sizeof(NODO));
	if(new == NULL)	{
		exit(NO_MEM);
	}

	new->info = info;
	new->prox = aux;
	hash_point->table[hash] = new;
	hash_point->ocupados	+= 1;

	return 0;
}

/**
 * Procura um utilizador da tabela de hash
 * @param hash_point Estrtura da tabela de hash
 * @param str Nome do utilizador a pesquisar
 * @return apontador para a informação associada ao elemento pedido
 */
void* hash_table_procura (HASH_TABLE *hash_point, void *key) {

	unsigned long hash;
	NODO *aux;
	void *h_key;

	hash = (hash_point->hash)(key);
	hash = hash % (hash_point->tamanho);

	if (hash_point->table[hash] == NULL)
		return NULL;

	for ( aux = hash_point->table[hash] ; aux ; aux = aux->prox) {
		h_key = (hash_point->getKey)(aux->info);
		if ((hash_point->compKey)(h_key,key) == 0)
			return (aux->info);
	}
	return;
}

/**
 * Remove um utilizador da tabela de hash
 * @param hash_point Estrutura da tabela de hash
 * @param key Chave a pesquisar na tabela
 */
int hash_table_remover (HASH_TABLE *hash_point, void *key) {

	unsigned long hash;
	NODO *aux,*aux2;
    void *h_key;

	hash = (hash_point->hash)(key);
	hash = hash % hash_point->tamanho;

	if (hash_point->table[hash] == NULL)
		return 0;

	aux = hash_point->table[hash];
	h_key = (hash_point->getKey)(aux->info);

	if((hash_point->compKey)(h_key,key) == 0)	{

		aux2=aux->prox;
		free(aux);
		hash_point->table[hash] = aux;

		hash_point->ocupados --;

		return 1;}
	for ( ; aux->prox; aux = aux->prox){
		h_key = (hash_point->getKey)(aux->prox->info);
		if((hash_point->compKey)(h_key,key) == 0){
            aux2 = aux->prox->prox;
            free (aux->prox);
            aux = aux2;
            hash_point->ocupados --;
            return 1;
        }
	}
	return 0;
}

int hash_table_map	(HASH_TABLE *hash_point, int (*f)(void *, void *), void *params){
	int i;
	NODO *aux;

	for(i=0; i < hash_point->tamanho ; i++){
		for( aux = hash_point->table[i] ; aux ; aux = aux->prox)
			f(aux->info, params); }
	return 0;
}

int hash_table_apagar(HASH_TABLE *hash_point){
	NODO *cabeca, *aux=NULL;
	int i;

	for( i=0 ; i < hash_point->tamanho ; i++){
		cabeca = hash_point->table[i];
		while (cabeca)	{
			aux = aux->prox;
			free(cabeca);
			cabeca = aux;
		}
	}
	return 0;
}

/*void hash_print(HASH_TABLE *hash_point, void (*f)(void *info)){
	int i;
	int vazio;
	NODO *aux;

	for(i=0; i < hash_point->tamanho ; i++){
		vazio =1;
		for(aux = hash_point->table[i] ; aux ; aux = aux->prox){
			vazio =0;
			printf("(%ud,%20s):", ((Index_Nome *)aux->info)->p, ((Index_Nome *)aux->info)->nome);
		 }
		if(vazio == 1) printf("[]");
		printf("[]\n");
	}
}*/

/*
 MELHOR FUNÇAO DE HASH PARA STRINGS:

 unsigned long
 hash(unsigned char *str)
 {
 unsigned long hash = 5381;
 int c;

 while (c = *str++)
 hash = ((hash << 5) + hash) + c; /* hash * 33 + c

 return hash;
 }*/